package tool.zyk.spring.bean;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import tool.zyk.configuration.SimpleRpcConfiguration;
import tool.zyk.connect.SimpleRpcInstance;
import tool.zyk.consts.RpcCommonConst;
import tool.zyk.net.NioClient;
import tool.zyk.spring.annotation.RpcConsumer;
import tool.zyk.stardand.RpcRequest;
import tool.zyk.stardand.RpcResponse;
import tool.zyk.utils.ReflectUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;

/**
 * @author feite.zyk
 * @time 2024/2/2 16:06
 * @description 代理对象自动注入
 */
@Slf4j
public class ConsumerAutoInjector implements BeanPostProcessor, ApplicationContextAware {
    private SimpleRpcConfiguration simpleRpcConfiguration;


    @SneakyThrows
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Class<?> aClass = bean.getClass();
        for (Field declaredField : aClass.getDeclaredFields()) {
            RpcConsumer annotation = declaredField.getAnnotation(RpcConsumer.class);
            if (annotation != null) {
                declaredField.setAccessible(true);
                Class<?> declaringClass = declaredField.getType();
                declaredField.set(bean, getObj(declaringClass));
                String serverName = annotation.serverName();
                //将服务者的名字缓存起来
                simpleRpcConfiguration.getProviderCache().put(declaringClass.getName(), serverName);
            }
        }

        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }


    private Object getObj(Class<?> declaringClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(declaringClass);
        enhancer.setCallback(new RpcMethodInvoke());
        return enhancer.create();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.simpleRpcConfiguration = applicationContext.getBean(SimpleRpcConfiguration.class);
    }

    private class RpcMethodInvoke implements MethodInterceptor {


        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            log.debug("simple-rpc 开始调用");
            long start = System.currentTimeMillis();
            NioClient nioClient = new NioClient(simpleRpcConfiguration);

            RpcRequest rpcRequest = getRpcRequest(o, method, objects);

            String originClassname = getOriginClassname(method);
            String providerApplicationName = Optional.ofNullable(simpleRpcConfiguration)
                    .map(SimpleRpcConfiguration::getProviderCache)
                    .map(map -> map.get(originClassname))
                    .orElseThrow(() -> new RuntimeException("未知的RPC提供者->" + originClassname));
            List<SimpleRpcInstance> simpleRpcInstances = simpleRpcConfiguration
                    .getServerConnectionFactory()
                    .getConnection()
                    .selectList(providerApplicationName, RpcCommonConst.DEFAULT_CLUSTER_NAME);
            SimpleRpcInstance simpleRpcInstance = loadBalance(simpleRpcInstances);

            RpcResponse response = nioClient.getResult(simpleRpcInstance.getIp(), simpleRpcInstance.getPort(), rpcRequest);
            log.debug("RPC调用结束,耗时:{}ms,返回值:{}", System.currentTimeMillis() - start, response);
            return response.getData();
        }

        private RpcRequest getRpcRequest(Object o, Method method, Object[] objects) {
            String originClassname = getOriginClassname(method);
            String methodCode = ReflectUtils.encodeMethodCode(method);
            RpcRequest rpcRequest = new RpcRequest();
            rpcRequest.setBeUseClass(originClassname);
            rpcRequest.setMethodCode(methodCode);
            rpcRequest.setArgs(objects);
            return rpcRequest;
        }

        private String getOriginClassname(Method method) {
            return method.getDeclaringClass().getName();
        }

        private SimpleRpcInstance loadBalance(List<SimpleRpcInstance> list) {
            return list.stream().findFirst().orElseThrow(() -> new RuntimeException("无效的负载均衡"));
        }
    }


}
